package com.ansion.basedb.database.zoom;


import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;


import com.ansion.basedb.dao.DaoLoadHelper;
import com.ansion.basedb.dao.DemoDao;
import com.ansion.basedb.dao.entity.DemoEntity;
import com.ansion.basedb.database.DatabaseKeyGenerator;
import com.ansion.basedb.database.TrackHelper;
import com.ansion.basedb.storage.secure.CipherKeyManager;
import com.ansion.constant.BConstant;
import com.ansion.log.VLog;
import com.ansion.utils.ContactUtil;
import com.ansion.utils.ResourceUtils;

import net.zetetic.database.sqlcipher.SQLiteConnection;
import net.zetetic.database.sqlcipher.SQLiteDatabaseHook;
import net.zetetic.database.sqlcipher.SupportOpenHelperFactory;

import java.io.File;
import java.nio.charset.StandardCharsets;

@Database(entities = {DemoEntity.class
}, version = 11, exportSchema = true)
public abstract class ThirdPartDataBase extends RoomDatabase {
    private static final String LICENSE_CODE = "OmNpZDowMDEzbzAwMDAyZnQxWThBQUk6cGxhdGZvcm06MTA6ZXhwaXJlOm5ldmVyOnZlcnNpb246MTpobWFjOjhiNWIwNDMwNGM0NDAyYjc2ZDgxN2UxZDFlZjZkMTJjNjI3MGYxYWM=";

    final static String DB_NAME_PREFIX = "t_common_db_";

    public abstract DemoDao getDemoDao();


    private static volatile ThirdPartDataBase thirdPartDataBase;

    private static String currentUserId;

    public static ThirdPartDataBase getDataBaseInstance() {
//        if (!ContactUtil.isLoggedIn()) {
            VLog.e("TAG_STORAGE", "you must login to use getThirdPartDataBase");
//        }
        return getDataBaseInstance(ContactUtil.getMyUid());
    }

    /***
     * 获取第三方数据库
     */
    public static ThirdPartDataBase getDataBaseInstance(String userId) {
        if (thirdPartDataBase != null && !TextUtils.equals(userId, currentUserId)) {
            release();
            VLog.i("TAG_STORAGE", "release database currentUserId %s,userId:%s", currentUserId, userId);
        }
        if (thirdPartDataBase == null) {
            synchronized (ThirdPartDataBase.class) {
                if (thirdPartDataBase == null) {
                    thirdPartDataBase = createDataBase(userId);
                    currentUserId = userId;

                }
            }
        }
        return thirdPartDataBase;
    }

    private static ThirdPartDataBase createDataBase(String userId) {
        Context context = ResourceUtils.getContext();
        SupportSQLiteOpenHelper.Factory openFactory = null;
        String dbNameV1 = getDbName(userId);
        String dbNameV2 = getDbNameV2(userId);
        File dbV1 = context.getDatabasePath(dbNameV1);
        String dbName = dbNameV1;
        if (DaoLoadHelper.sqlcipherEnable) {
            String pwd = "";
            if (dbV1.isFile() && dbV1.exists()) {
                VLog.i("TAG_STORAGE", "sqlcipherEnable then use SupportFactory V1");
                pwd = CipherKeyManager.getInst().getPassword(dbNameV1);
                dbName = dbNameV1;
            } else {
                VLog.i("TAG_STORAGE", "sqlcipherEnable then use SupportFactory V2");
                DatabaseKeyGenerator.DatabaseKeyResult result = getDatabaseKeyResult(userId, dbNameV2);
                pwd = result.base64EncodedKey;
                dbName = dbNameV2;
            }
            openFactory = new SupportOpenHelperFactory(pwd.getBytes(StandardCharsets.UTF_8), getHook(), true);
            VLog.d("TAG_STORAGE", "getThirdPartDataBase:%s,pwd:%s", dbName, pwd);
        }


        VLog.d("TAG_STORAGE", "getThirdPartDataBase:%s", dbName);

        ThirdPartDataBase dataBase = buildRoomDataBase(context, dbName, openFactory);
        try {
            long start = System.currentTimeMillis();
            dataBase.getOpenHelper().getWritableDatabase();
            long cost = System.currentTimeMillis() - start;
            VLog.i("TAG_STORAGE", "getThirdPartDataBase:%s cost:%d", dbName, cost);
        } catch (Exception e) {
            String message = e.getMessage() == null ? "" : e.getMessage();
            if (message.contains("file is not a database (code 26)")) {
                TrackHelper.trackError(BConstant.FootPrint.E2EE.TRACK_EVENT_NAME, "recover_db", message, userId);
                try {
                    context.deleteDatabase(dbName);
                } catch (Exception e2) {
                    VLog.e("TAG_STORAGE", "deleteDatabase error:%s", e2.getMessage());
                }
                VLog.FileLog.i("TAG_STORAGE", "database password error, delete the old one:%s,and create a new one with version v2:%s", dbName, dbNameV2);
                DatabaseKeyGenerator.DatabaseKeyResult result = getDatabaseKeyResult(userId, dbNameV2);
                openFactory = new SupportOpenHelperFactory(result.base64EncodedKey.getBytes(StandardCharsets.UTF_8), getHook(), true);
                dataBase = buildRoomDataBase(context, dbNameV2, openFactory);
//                EventBus.INSTANCE.post(new Event(BConstant.E2EE.EVENT_RECOVER_DB));
            }
        }

        return dataBase;
    }

    private static @NonNull DatabaseKeyGenerator.DatabaseKeyResult getDatabaseKeyResult(String userId, String dbNameV2) {
        DatabaseKeyGenerator.DatabaseKeyResult result = DatabaseKeyGenerator.generateDatabaseKeyForUID(dbNameV2);
        if (result.error != null) {
            String message = result.error.getMessage() == null ? "" : result.error.getMessage();
            TrackHelper.trackError(BConstant.FootPrint.E2EE.TRACK_EVENT_NAME, "DatabaseKeyGenerateFail", message, userId);
            throw new RuntimeException("generateDatabaseKeyForUID error:" + message);
        }
        if (TextUtils.isEmpty(result.base64EncodedKey)) {
            TrackHelper.trackError(BConstant.FootPrint.E2EE.TRACK_EVENT_NAME, "DatabaseKeyGenerateFail", "base64EncodedKey is empty", userId);
            throw new RuntimeException("generateDatabaseKeyForUID error:base64EncodedKey is empty");
        }
        return result;
    }

    private static @NonNull ThirdPartDataBase buildRoomDataBase(Context context, String dbName, SupportSQLiteOpenHelper.Factory openFactory) {
        return Room.databaseBuilder(context, ThirdPartDataBase.class, dbName)
                .openHelperFactory(openFactory)
                .addMigrations(MIGRATION_2_3)
                .addMigrations(MIGRATION_3_4)
                .addMigrations(MIGRATION_4_5)
                .addMigrations(MIGRATION_5_6)
                .addMigrations(MIGRATION_6_7)
                .addMigrations(MIGRATION_7_8)
                .addMigrations(MIGRATION_8_9)
                .addMigrations(MIGRATION_9_10)
                .addMigrations(MIGRATION_10_11)
                .build();
    }


    private static SQLiteDatabaseHook getHook() {
        return new SQLiteDatabaseHook() {
            public void preKey(SQLiteConnection connection) {
                connection.executeForString(String.format("PRAGMA cipher_license = '%s';", LICENSE_CODE), new Object[]{}, null);
            }

            public void postKey(SQLiteConnection connection) {
            }
        };
    }

    //支持未登录的时候查数据


    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_2_3 start");
            // Since we didn't alter the table, there's nothing else to do here.
            database.execSQL("CREATE TABLE IF NOT EXISTS `t_check_in` (`recordId` TEXT NOT NULL, `count` INTEGER NOT NULL, `recordTime` INTEGER NOT NULL, `expireTime` INTEGER NOT NULL, `busType` INTEGER NOT NULL, PRIMARY KEY(`recordId`))");
            database.execSQL("CREATE INDEX IF NOT EXISTS `index_t_check_in_expireTime` ON `t_check_in` (`expireTime`)");
            VLog.i("TAG_STORAGE", "MIGRATION_2_3 end");
        }
    };

    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_3_4 start");
            database.execSQL("CREATE TABLE IF NOT EXISTS `t_del_msg` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `sessionId` TEXT, `fromUid` TEXT, `msgId` INTEGER NOT NULL)");
            database.execSQL("CREATE INDEX IF NOT EXISTS `index_t_del_msg_sessionId_fromUid_msgId` ON `t_del_msg` (`sessionId`, `fromUid`, `msgId`)");
            VLog.i("TAG_STORAGE", "MIGRATION_3_4 end");
        }
    };

    static final Migration MIGRATION_4_5 = new Migration(4, 5) {//兼容线上
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_4_5 start");
            // Since we didn't alter the table, there's nothing else to do here.
            database.execSQL("CREATE TABLE IF NOT EXISTS `t_track` (`type` TEXT NOT NULL, `id` INTEGER NOT NULL, `createTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `payload` BLOB, PRIMARY KEY(`type`, `id`))");
            VLog.i("TAG_STORAGE", "MIGRATION_4_5 end");
        }
    };

    static final Migration MIGRATION_5_6 = new Migration(5, 6) {//e2ee升级
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_5_6 start");
            database.execSQL("CREATE TABLE IF NOT EXISTS `IdentityKeyEntity` (`recordId` TEXT NOT NULL, `uid` TEXT, `identityKeyString` TEXT, `timestamp` INTEGER NOT NULL, `verifiedStatus` INTEGER NOT NULL, `nonBlockingApproval` INTEGER NOT NULL, `firstUse` INTEGER NOT NULL, `deviceId` INTEGER NOT NULL, PRIMARY KEY(`recordId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `SignedPreKeyEntity` (`keyId` INTEGER NOT NULL, `publicKey` TEXT, `privateKey` TEXT, `signature` TEXT, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`keyId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `PreKeyEntity` (`keyId` INTEGER NOT NULL, `publicKey` TEXT, `privateKey` TEXT, PRIMARY KEY(`keyId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `SessionEntity` (`recordId` TEXT NOT NULL, `uid` TEXT, `deviceId` INTEGER NOT NULL, `sessionRecord` TEXT, PRIMARY KEY(`recordId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `DeviceKeyVersionEntity` (`userId` TEXT NOT NULL, `keyVersionJson` TEXT, `e2eeEnable` INTEGER NOT NULL, `lastUpdateTime` INTEGER NOT NULL, PRIMARY KEY(`userId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `SessionKeyVersionEntity` (`userId` TEXT NOT NULL, `keyVersionJson` TEXT, PRIMARY KEY(`userId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `ResendMessageEntity` (`recordId` TEXT NOT NULL, `toUid` TEXT, `deviceId` INTEGER NOT NULL, `msgId` TEXT, `targetUid` TEXT, PRIMARY KEY(`recordId`))");
            database.execSQL("CREATE INDEX IF NOT EXISTS `index_ResendMessageEntity_targetUid_deviceId` ON `ResendMessageEntity` (`targetUid`, `deviceId`)");
            database.execSQL("CREATE TABLE IF NOT EXISTS `DecryptFailMessageEntity` (`recordId` TEXT NOT NULL, `fromUid` TEXT, `deviceId` INTEGER NOT NULL, `pbType` INTEGER NOT NULL, `serverTime` INTEGER NOT NULL, `toUid` TEXT, `msgId` TEXT, PRIMARY KEY(`recordId`))");
            VLog.i("TAG_STORAGE", "MIGRATION_5_6 end");
        }
    };

    static final Migration MIGRATION_6_7 = new Migration(6, 7) {//e2ee升级
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_6_7 start");
            database.execSQL("CREATE TABLE IF NOT EXISTS `AllServiceEntity` (`appId` TEXT NOT NULL, `serviceType` INTEGER NOT NULL, `headUrl` TEXT, `linkUrl` TEXT, `name` TEXT, `tag` TEXT, `desc` TEXT, `category` TEXT, `weight` INTEGER NOT NULL, PRIMARY KEY(`appId`))");
            database.execSQL("CREATE TABLE IF NOT EXISTS `RecentServiceEntity` (`id` TEXT NOT NULL, `identify` TEXT, `reddotKey` TEXT, `reddotVisible` INTEGER NOT NULL, `reddotSwitch` INTEGER NOT NULL, `visible` INTEGER NOT NULL, `reddotInterval` INTEGER NOT NULL, `describe` TEXT, `reddotType` TEXT, `title` TEXT, `iconResource` INTEGER NOT NULL, `iconUrl` TEXT, `link` TEXT, `timestamp` INTEGER NOT NULL, `favorite` INTEGER NOT NULL, `badge` TEXT, `lastBadge` TEXT, `inCc` INTEGER NOT NULL, `inCache` INTEGER NOT NULL, `useTimestamp` INTEGER NOT NULL, `category` TEXT, PRIMARY KEY(`id`))");
            VLog.i("TAG_STORAGE", "MIGRATION_6_7 end");
        }
    };

    static final Migration MIGRATION_7_8 = new Migration(7, 8) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_7_8 start");
            database.execSQL("ALTER TABLE `AllServiceEntity` ADD COLUMN `icons` TEXT");
            VLog.i("TAG_STORAGE", "MIGRATION_7_8 end");
        }
    };

    static final Migration MIGRATION_8_9 = new Migration(8, 9) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_8_9 start");
            database.execSQL("ALTER TABLE RecentServiceEntity ADD COLUMN `type` INTEGER NOT NULL DEFAULT 0");
            VLog.i("TAG_STORAGE", "MIGRATION_8_9 end");
        }
    };

    static final Migration MIGRATION_9_10 = new Migration(9, 10) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_9_10 start");
            database.execSQL("CREATE TABLE IF NOT EXISTS `MsgPayloadEntity` (`msgId` TEXT NOT NULL, `payload` BLOB, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`msgId`))");
            database.execSQL("CREATE INDEX IF NOT EXISTS `index_MsgPayloadEntity_timestamp` ON `MsgPayloadEntity` (`timestamp`)");
            VLog.i("TAG_STORAGE", "MIGRATION_9_10 end");
        }
    };

    static final Migration MIGRATION_10_11 = new Migration(10, 11) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            VLog.i("TAG_STORAGE", "MIGRATION_10_11 start");
            database.execSQL("CREATE TABLE IF NOT EXISTS `t_steps` (`timestamp` INTEGER NOT NULL, `upload` INTEGER NOT NULL, `steps` INTEGER NOT NULL, `counter` INTEGER NOT NULL, `upload_date` TEXT NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`upload_date`, `source`))");
            database.execSQL("CREATE INDEX IF NOT EXISTS index_t_steps_counter_timestamp ON t_steps(counter, timestamp)");
            VLog.i("TAG_STORAGE", "MIGRATION_10_11 end");
        }
    };

    public static void release() {
        VLog.i("TAG_STORAGE", "release database:%s", getDbName(currentUserId));
        if (thirdPartDataBase != null)
            thirdPartDataBase.close();
        thirdPartDataBase = null;
        currentUserId = null;
    }


    private static String getDbName(String uid) {
        String dbName;
        if (!DaoLoadHelper.sqlcipherEnable) {//不支持加密就用不加密的数据库文件
            dbName = DB_NAME_PREFIX + uid + "_no_enc.db";
        } else {
            dbName = DB_NAME_PREFIX + uid + ".db";
        }
        return dbName;
    }


    private static String getDbNameV2(String uid) {
        return DB_NAME_PREFIX + uid + "_V2" + ".db";
    }


    public static void deleteAccount(long uid) {
        String dbName = getDbName(String.valueOf(uid));
        File dbFile = ResourceUtils.getContext().getDatabasePath(dbName);
        if (null != dbFile && dbFile.exists()) {
            ResourceUtils.getContext().deleteDatabase(dbName);
        }
        String dbNameV2 = getDbNameV2(String.valueOf(uid));
        File dbFileV2 = ResourceUtils.getContext().getDatabasePath(dbNameV2);
        if (null != dbFileV2 && dbFileV2.exists()) {
            ResourceUtils.getContext().deleteDatabase(dbNameV2);
        }
    }
}
